#include "gpsinterface.h"
#include "math.h"

template <class T>
void ForceInRange(const T& min,T& value,const T& max)
{
   if ( value < min )
      value += max;
   else if ( max <= value )
      value -= max;
}

gpsinterface::gpsinterface(QObject *parent) :
    QObject(parent)
{
    m_fLong = 0;
    m_fLat = 0;
    m_Sunrise = QTime(0,0,0,0);
    m_Sunset = QTime(0,0,0,0);
    currDateTime = QDateTime::currentDateTime();
    night = false;
    day = true;
    gpsDataloger = new gpsdProc();
    connect (gpsDataloger, SIGNAL(changeData()), this, SLOT(calculate()));
    gpsDataloger->start();
}

void gpsinterface::calculate()
{
    if (gpsDataloger->longitude == 0 || gpsDataloger->latitude == 0) {
        m_Sunrise = QTime(18,0,0,0);
        m_Sunset = QTime(9,0,0,0);
    } else {
        m_fLong = gpsDataloger->longitude;
        m_fLat  = gpsDataloger->latitude;
        ComputeSunriset();
    }

    //рассвет
    if (currDateTime.time() >= m_Sunrise && currDateTime.time() < m_Sunset) {
        day = true;
        night = false;
        emit riseset();
    }

    if (currDateTime.time() >= m_Sunset && currDateTime.time() > m_Sunrise) {
        day = false;
        night = true;
        emit riseset();
    }
}

bool gpsinterface::nowNight() {
    return night;
}

bool gpsinterface::nowDay() {
    return day;
}


//* Include in roadnav and libroadnav */
double gpsinterface::DegreesToRadians(double x)
{
        return x * DEG_2_RAD;
}

double gpsinterface::RadiansToDegrees(double x)
{
   return x * RAD_2_DEG;
}

void gpsinterface::ComputeSunriset()
{
    QDateTime t;
    t.setTimeSpec(Qt::UTC);
    t.setTime_t(gpsDataloger->time);

    long local_offset = QDateTime::currentDateTime().time().hour() - QDateTime::currentDateTimeUtc().time().hour();

    currDateTime = QDateTime(t.date(),QTime(t.time().hour()+local_offset,t.time().minute(),t.time().second()));

    int year = t.date().year();
    int month = t.date().month() + 1;
    int day = t.date().day();

    double zenith = 96.;//civil

    // Compute day of year
    int N1 = (int) (floor((double) 275 * month / 9));
    int N2 = (int) (floor((double) (month + 9) / 12));
    int N3 = (int) (1 + floor((double) (year - 4 * floor((double) year / 4) + 2) / 3));

    int N = N1 - (N2 * N3) + day - 30;

    // Convert the m_fLong to hour value and calculate an approximate time
    double lngHour = m_fLong / 15.;

    double t_rise = N + (( 6 - lngHour) / 24);
    double t_set  = N + ((18 - lngHour) / 24);

    // Calculate the Sun's mean anomaly
    double M_rise = (0.9856 * t_rise) - 3.289;
    double M_set  = (0.9856 * t_set)  - 3.289;

    // Calculate the Sun's true longitude
    double L_rise = M_rise + (1.916 * sin(DegreesToRadians(M_rise))) +
            (0.020 * sin(DegreesToRadians(2 * M_rise))) + 282.634;

    double L_set = M_set + (1.916 * sin(DegreesToRadians(M_set))) +
            (0.020 * sin(DegreesToRadians(2 * M_set))) + 282.634;

    ForceInRange(0.,L_rise,360.);
    ForceInRange(0.,L_set, 360.);

    // Calculate the Sun's right ascension
    double RA_rise = RadiansToDegrees(atan(0.91764 * tan(DegreesToRadians(L_rise))));
    double RA_set  = RadiansToDegrees(atan(0.91764 * tan(DegreesToRadians(L_set))));

    // Right ascension value needs to be in the same quadrant as L
    double Lquad_rise = (floor(L_rise / 90)) * 90;
    double Lquad_set  = (floor(L_set  / 90)) * 90;

    double RAquad_rise = (floor(RA_rise / 90)) * 90;
    double RAquad_set  = (floor(RA_set  / 90)) * 90;

    RA_rise += Lquad_rise - RAquad_rise;
    RA_set  += Lquad_set  - RAquad_set;

    // Right ascension value needs to be convered into hours
    RA_rise /= 15.;
    RA_set  /= 15.;

    // Calculate the Sun's declination
    double sinDec_rise = 0.39782 * sin(DegreesToRadians(L_rise));
    double cosDec_rise = cos(asin(sinDec_rise));

    double sinDec_set = 0.39782 * sin(DegreesToRadians(L_set));
    double cosDec_set = cos(asin(sinDec_set));

    // Calculate the Sun's local hour angle
    double cosH_rise = (cos(DegreesToRadians(zenith))
                        - sinDec_rise * sin(DegreesToRadians(m_fLat)))
            /(cosDec_rise * cos(DegreesToRadians(m_fLat)));

    double cosH_set = (cos(DegreesToRadians(zenith))
                       - sinDec_set * sin(DegreesToRadians(m_fLat)))
            /(cosDec_set * cos(DegreesToRadians(m_fLat)));

    // Finish calcuating H and convert into hours
    double H_rise = 360 - RadiansToDegrees(acos(cosH_rise));
    double H_set  = RadiansToDegrees(acos(cosH_set));

    H_rise /= 15.;
    H_set  /= 15.;

    // Calculate local mean time of rising/settings
    double T_rise = H_rise + RA_rise - (0.06571 * t_rise) - 6.622;
    double T_set  = H_set  + RA_set  - (0.06571 * t_set)  - 6.622;

    // Adjust back to UTC
    double UT_rise = T_rise - lngHour;
    double UT_set  = T_set  - lngHour;

    // Adjust to 24 hours
    ForceInRange(0.,UT_rise,24.);
    ForceInRange(0.,UT_set, 24.);

    double LT_rise = UT_rise + local_offset;
    double LT_set  = UT_set  + local_offset;

    // Adjust to 24 hours
    ForceInRange(0.,LT_rise,24.);
    ForceInRange(0.,LT_set, 24.);

    int h = (int) floor(LT_rise);
    double m = 60 * (LT_rise - h);
    double s = floor(60 * (m - floor(m)));
    m = floor(m);
    m_Sunrise = QTime((int) h, (int) m, (int) s, 0);

    h = (int) floor(LT_set);
    m = 60 * (LT_set - h);
    s = floor(60 * (m - floor(m)));
    m = floor(m);
    m_Sunset = QTime((int) h, (int) m, (int) s, 0);
}

gpsdProc::gpsdProc()
: QThread()
{
    gpsdata = NULL;
    pipe_fd = 0;
    longitude = 0;
    latitude = 0;
    time = 0;
}

gpsdProc::~gpsdProc()
{
}

void gpsdProc::run()
{
    gpsdata = gps_open( "localhost", DEFAULT_GPSD_PORT );

    if( !gpsdata )
    {
        emit changeData();
        return;
    }                            // if

    gps_stream( gpsdata, WATCH_NEWSTYLE, NULL );

    fd_set fds;

    while( true )
    {
        // sleep until either GPSD or our controlling thread has data for us.
        FD_ZERO(&fds);
        FD_SET(gpsdata->gps_fd, &fds);
        int nfds = (pipe_fd > gpsdata->gps_fd ? pipe_fd : gpsdata->gps_fd) + 1;
        int data = select(nfds, &fds, NULL, NULL, NULL);

        if( data == -1 )
        {
            break;
        }                        // if

        else if( data )
        {
            if( FD_ISSET( pipe_fd, &fds ) )
            {
                char s;
                read( pipe_fd, &s, 1 );
                break;
            }                    // if
            else if( FD_ISSET( gpsdata->gps_fd, &fds ) )
            {
                gps_poll( gpsdata );
                if( !decodeData() ) break;
            }                    // else if
        }                        // else if
    }                            // while
    emit changeData();
    gps_close( gpsdata );
}


bool gpsdProc::decodeData()
{
    // see, if it's interesting
    if( gpsdata->fix.time == 0 ) {
        return true;
        emit changeData();
    }
    static const gps_mask_t interesting_mask = TIME_SET | LATLON_SET
        | ALTITUDE_SET | SPEED_SET | TRACK_SET | STATUS_SET | MODE_SET
        | HERR_SET | VERR_SET | ONLINE_SET;

    if( (gpsdata->set & interesting_mask) == 0 ) {
        emit changeData();
        return true;
    }

       longitude = gpsdata->fix.longitude;
       latitude = gpsdata->fix.latitude;
       time = gpsdata->fix.time;

    emit changeData();

    return true;
}


